package org.sam.swing;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;

import javax.swing.AbstractButton;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.event.EventListenerList;

/**
 * checkbox组控件
 * 
 * @author sam
 *
 * @param <V> 绑定值
 * @param <T> 显示值
 */
public class JSCheckBoxGroup<V, T> extends JPanel {

	private static final long serialVersionUID = 1L;

	/**
	 * 当前绑定的数据对象
	 */
	private final Map<V, T> values = new LinkedHashMap<>();

	/**
	 * 选中操作
	 */
	private ActionSelectionListener actionHandler;
	
	/**
	 * 为了缓存选中值使用
	 */
	private Collection<V> selectedValues;

	/**
	 * Create a default JXRadioGroup with a default layout axis of
	 * {@link BoxLayout#X_AXIS}.
	 */
	public JSCheckBoxGroup() {
		super();
		setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
	}

	/**
	 * Create a default JXRadioGroup with a default layout axis of
	 * {@link BoxLayout#X_AXIS}.
	 * 
	 * @param values the list of values used to create the group.
	 */
	public JSCheckBoxGroup(Map<V, T> values) {
		this();
		for (Entry<V, T> entry : values.entrySet()) {
			add(entry);
		}
	}

	/**
	 * 默认的选中对象
	 * 
	 * @param values      数据列表
	 * @param selectValue 选中的数据
	 */
	public JSCheckBoxGroup(Map<V, T> values, Collection<V> selectValue) {
		this();
		for (Entry<V, T> entry : values.entrySet()) {
			add(entry);
		}
		this.setSelectedValue(selectValue);
	}

	/**
	 * Convenience factory method. Reduces code clutter when dealing with generics.
	 * 
	 * @param values the map of values used to create the group.
	 */
	public static <V, T> JSCheckBoxGroup<V, T> create(Map<V, T> values) {
		return new JSCheckBoxGroup<V, T>(values);
	}

	/**
	 * Set the layout axis of the radio group.
	 * 
	 * @param axis values from {@link BoxLayout}.
	 */
	public void setLayoutAxis(int axis) {
		setLayout(new BoxLayout(this, axis));
	}

	/**
	 * Sets the values backing this group. This replaces the current set of values
	 * with the new set.
	 * 
	 * @param radioValues the new backing values for this group
	 */
	public void setValues(Map<V, T> values) {
		clearAll();
		for (Entry<V, T> entry : values.entrySet()) {
			add(entry);
		}
	}

	/**
	 * clear all item
	 */
	private void clearAll() {
		values.clear();
		// remove all the child components
		removeAll();
	}

	/**
	 * 增加一个数据项到系统中的方法
	 * 
	 * @param entry
	 */
	public void add(Entry<V, T> entry) {

		if (entry == null)
			throw new IllegalArgumentException("key-value is not can be null");

		if (entry.getKey() instanceof AbstractButton) {
			values.put(entry.getKey(), entry.getValue());
			addButton((AbstractButton) entry.getKey());
		} else {
			values.put(entry.getKey(), entry.getValue());
			addButton(new JSCheckBox<V>("" + entry.getValue(), entry.getKey()));
		}
	}

	/**
	 * 增加一个单选按钮的方法
	 * 
	 * @param button 按钮
	 */
	private void addButton(AbstractButton button) {
		super.add(button);
		if (actionHandler == null) {
			actionHandler = new ActionSelectionListener();
		}
		button.setBackground(null); // 设置了这个属性，背景色就会变成透明，不解
		button.addActionListener(actionHandler);
		button.addItemListener(actionHandler);
	}

	/**
	 * 消息通知
	 * 
	 * @author sam
	 *
	 */
	private class ActionSelectionListener implements ActionListener, ItemListener {
		@Override
		public void actionPerformed(ActionEvent e) {
			fireActionEvent(e);
		}

		@Override
		public void itemStateChanged(ItemEvent e) {
			fireActionEvent(null);
		}
	}

	/**
	 * 获取按钮列表，这个方法好脱裤子放屁
	 * 
	 * @return
	 */
	private AbstractButton[] getButtonComponents() {
		final Component[] children = getComponents();
		final List<AbstractButton> buttons = new ArrayList<AbstractButton>();
		for (int i = 0; i < children.length; i++) {
			if (children[i] instanceof AbstractButton) {
				buttons.add((AbstractButton) children[i]);
			}
		}
		return buttons.toArray(new AbstractButton[buttons.size()]);
	}


	/**
	 * The currently selected value.
	 * 
	 * @return the current value
	 */
	@SuppressWarnings("unchecked")
	public Collection<V> getSelectedValue() {
		final AbstractButton[] buttonComponents = getButtonComponents();
		
		Collection<V> result = null;
		if (selectedValues != null) {
			if(selectedValues instanceof List) {
				result = new ArrayList<>();
			} else if (selectedValues instanceof Set) {
				result = new TreeSet<>();
			} else {
				result = new ArrayList<>();
			}
		}
		
		for (int i = 0; i < buttonComponents.length; i++) {
			if (buttonComponents[i] instanceof JSCheckBox && buttonComponents[i].isSelected()) {
				JSCheckBox<V> c = (JSCheckBox<V>) buttonComponents[i];
				result.add(c.getTag());
			}
		}
		return result;
	}

	/**
	 * Selects the supplied value.
	 * 
	 * @param value the value to select
	 */
	@SuppressWarnings("unchecked")
	public void setSelectedValue(Collection<V> value) {
		if (value == null || value.isEmpty())
			return;
		
		this.selectedValues = value;

		AbstractButton[] buttons = getButtonComponents();
		for (AbstractButton button : buttons) {
			if (button instanceof JSCheckBox) {
				JSCheckBox<V> b = (JSCheckBox<V>) button;
				button.setSelected(value.contains(b.getTag()));
			} else {
				button.setSelected(value.contains(button.getText()));
			}
		}
	}

	/**
	 * Retrieve the child button by index.
	 */
	public AbstractButton getChildButton(int index) {
		return getButtonComponents()[index];
	}

	/**
	 * Retrieve the child button that represents this value.
	 */
	public AbstractButton getChildButton(V value) {
		T t = values.get(value);

		AbstractButton[] buttons = getButtonComponents();
		for (AbstractButton button : buttons) {
			if (button.getText().equals(t))
				return button;
		}
		return null;
	}

	/**
	 * Get the number of child buttons.
	 */
	public int getChildButtonCount() {
		return getButtonComponents().length;
	}

	/**
	 * Adds an <code>ActionListener</code>.
	 * <p>
	 * The <code>ActionListener</code> will receive an <code>ActionEvent</code> when
	 * a selection has been made.
	 *
	 * @param l the <code>ActionListener</code> that is to be notified
	 * @see #setSelectedValue(Object)
	 */
	public void addActionListener(ActionListener l) {
		listenerList.add(ActionListener.class, l);
	}

	/**
	 * Removes an <code>ActionListener</code>.
	 * 
	 * @param l the <code>ActionListener</code> to remove
	 */
	public void removeActionListener(ActionListener l) {
		listenerList.remove(ActionListener.class, l);
	}

	/**
	 * Returns an array of all the <code>ActionListener</code>s added to this
	 * JRadioGroup with addActionListener().
	 *
	 * @return all of the <code>ActionListener</code>s added or an empty array if no
	 *         listeners have been added
	 */
	public ActionListener[] getActionListeners() {
		return listenerList.getListeners(ActionListener.class);
	}

	/**
	 * Notifies all listeners that have registered interest for notification on this
	 * event type.
	 * 
	 * @param e the event to pass to the listeners
	 * @see EventListenerList
	 */
	protected void fireActionEvent(ActionEvent e) {
		for (ActionListener l : getActionListeners()) {
			l.actionPerformed(e);
		}
	}

	/**
	 * Enable/disable all of the child buttons
	 * 
	 * @see JComponent#setEnabled(boolean)
	 */
	@Override
	public void setEnabled(boolean enabled) {
		super.setEnabled(enabled);
		AbstractButton[] buttonComponents = this.getButtonComponents();
		if (buttonComponents != null) {
			for(AbstractButton b : buttonComponents) {
				b.setEnabled(enabled);
			}
		}
	}

}
